#include <cstring>
#include <sys/select.h>

#include "GameControl.h"
#include "../tools/Point.h"
#include "../Error.h"
#include "../tools/Time.h"
#include "../tools/Log.h"
#include "commands/Game_Init.h"
#include "commands/Game_LengthenFrame.h"
#include "network/GameNetwork.h"

GameControl* GameControl::thisInstance = NULL;

// NOTE: Command latency must be greater than game_frame_length or the game will lock. (It should be 2-3 times greater).
GameControl::GameControl() :
	g(), network(NULL), game_thread(), game_time(0), game_frame_length(200), last_increase(0), command_latency(750), gamePaused(false),
	run(true), command_queue(), command_buffer()
{
	thisInstance = this;
}

unsigned long GameControl::get_game_time() { return game_time; }

unsigned long GameControl::get_command_horizon() { return game_time + command_latency; }

/*	Network init must be called after Game is instantiated because the network looks for an instance of game immediately
	(before Game's constructor finishes running).
	
	node_neighborhood and node_id are parameters for a network game
*/
void GameControl::init(const string game_config_file, const GameConf& game_conf)
{
	if (game_conf.network_conf != NULL)		// Start a network game
	{
		network = new GameNetwork(command_latency);
		network->init(*(game_conf.network_conf));
	}
	
	g.init(game_config_file, game_conf);
	
	CHECK(pthread_create(&game_thread, NULL, game_thread_main, this));
}

GameControl::~GameControl()
{
	void* d;
	run = false;
	CHECK(pthread_join(game_thread, &d));
	
	if(network) {
		delete network;
		network = NULL;
	}
}

void GameControl::pauseGame() {
	gamePaused = true;
}
void GameControl::resumeGame() {
	gamePaused = false;
}

void* GameControl::game_thread_main(void* data)
{
	GameControl* game_control = (GameControl*) data;
	game_control->main_loop();
	return NULL;
}

void GameControl::let_network_get_ahead()
{
	lout << "Waiting on network." << endl;
	while(run && network->ok_timestamp < (game_time + command_latency))
		sched_yield();
}

void GameControl::main_loop()
{
	if (!network || (network && network->i_am_host()))
		appendGameCommand(new Game_Init(time(NULL)));
	
	if (network)
		let_network_get_ahead();
	
	unsigned num_slow_frames = 0;
	
	while(run)
	{
		static vector <GameCommand *> commands_to_execute;		// So we don't execute while we have the lock on command_queue
		
		/*	Game caught up to Network. We can't continue until Network gives us another frame. But we choose to wait even
			longer than that, as we don't want to run up on Network every frame.
		*/
		if (network && ((game_time + game_frame_length) >= network->ok_timestamp))		// Waiting on peers
			let_network_get_ahead();
		
		unsigned frame_begin = getMilliseconds();
			
			// Build list of commands to execute this game frame
			while (!command_queue.empty() && (command_queue.top()->timestamp < (game_time + game_frame_length)))
				commands_to_execute.push_back(command_queue.pop());
			
			// TODO Sort commands by node_id or some other criteria
			for (unsigned i = 0; i < commands_to_execute.size(); i ++)
			{
				lout << "Executing " << commands_to_execute[i]->toString() << endl;
				
				commands_to_execute[i]->onExecute();
				delete commands_to_execute[i];
			}
			
			commands_to_execute.clear();
			if(!gamePaused) {
				g.step(game_frame_length);
				game_time += game_frame_length;
			}
			
		unsigned frame_end = getMilliseconds();
		
		if ((frame_end - frame_begin) > game_frame_length)		// Frame took too long to simulate
		{
			lout << "Frame took too long (" << (frame_end - frame_begin) << " > " << game_frame_length << ")" << endl;
			
			num_slow_frames ++;
			
			if ((num_slow_frames % NUM_SLOW_FRAMES_BEFORE_INCREASE) == 0)
				appendGameCommand(new Game_LengthenFrame());
		}
		
		/*	Block for as long as this frame was supposed to take. Note: sleep doesn't provide enough granularity. It operates
			on integer number of seconds when we require millisecond precision.
		*/
		while ((getMilliseconds() - frame_begin) < game_frame_length)
			sched_yield();
		
		if (game_frame_length > (1000 / MAX_FPS))	// If allowed, decrease game_frame_length by one ms every frame
			game_frame_length --;
		
		//lout << (++ frame_num) << ": " << game_time << ", " << game_frame_length << ", " << network->ok_timestamp << endl;
	}
}

#include "commands/Game_Resume.h"
// Add a command received from a local input source (mouse, keyboard, script, etc.)
void GameControl::appendGameCommand(GameCommand* command)
{
	//This ensures that resume is called when issued
	if(dynamic_cast<Game_Resume*>(command))
		command->timestamp = 0;
	
	/*	Commands passed with a non-zero timestamp will be executed with the timestamp they already have. Commands with
		a timestamp of 0 will be executed command_latency milliseconds after game's current game_time.
	*/
	else if (command->timestamp == 0)
		command->timestamp = game_time + command_latency;		// Execute this command in the future
	
	command_buffer.push_back(command);
	command_queue.push(command);
}

/*	game_frame_length is increased at most once per frame. This covers the case in which 2 peers request that frame
	length be increased in the same frame (avoids double increase).
*/
void GameControl::increase_frame_length()
{
	if (game_time >= (last_increase + game_frame_length))
	{
		double increase_factor = 1 + (FRAME_LENGTH_PERCENT_INCREASE / 100.0);
		
		game_frame_length *= increase_factor;		// Increase game frame length by 10%, if possible
		last_increase = game_time;
	}
}

void GameControl::onWindowResize(float W, float H) {
	g.onWindowResize(W,H);
}
